package com.yhb.channel.impl.ANYPAY.http;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.yhb.channel.impl.ANYPAY.AnyPayChannel;
import com.yhb.channel.impl.ANYPAY.AnyPayConstant;
import com.yhb.channel.impl.ANYPAY.AnyPayException;
import com.yhb.channel.impl.ANYPAY.http.request.AnyPayBaseRequest;
import com.yhb.channel.impl.ANYPAY.http.response.AnyPayBaseCallbackRespModel;
import com.yhb.channel.impl.ANYPAY.http.response.AnyPayBaseRespModel;
import com.yhb.channel.impl.ANYPAY.http.utils.*;
import com.yhb.channel.provider.ChannelBaseService;
import com.yhb.channel.utils.ChannelJSONUtil;
import com.yhb.channel.utils.ChannelLogger;
import com.yhb.channel.utils.ConfigGetter;
import lombok.extern.log4j.Log4j;
import lombok.var;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.util.io.pem.PemReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sun.misc.BASE64Encoder;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

@Log4j
@Service
public class AnyPayHttpClient {

    public static final String signAldo = "SHA1WithRSA";
    public static final int TIME_OUT = 60;

    private ConfigGetter configGetter = null;
    private ChannelLogger logger = null;
    public String mchtNo;
    public String keyStorePath;
    public String rsaPwd;
    public String perfectPayUrl;
    public String aesKey;

    @Autowired
    private ChannelBaseService channelBaseService;

    /* 启动方法 */
    @PostConstruct
    private void init() {
        configGetter = new ConfigGetter("AnyPay", channelBaseService.iUtilProvider);
        mchtNo = configGetter.get("mchtNo");
        keyStorePath = configGetter.get("keyStorePath");
        aesKey = configGetter.get("aesKey");
        perfectPayUrl = configGetter.get("perfectPayUrl");
        rsaPwd = configGetter.get("rsaPwd");
        logger = new ChannelLogger(AnyPayChannel.CHANNEL_NAME, AnyPayException.class, channelBaseService.iUtilProvider);
    }

    private void saveRequestFlow(String flow) {
        channelBaseService.iUtilProvider.savePayChannelFlow(AnyPayChannel.CHANNEL_NAME + "---", flow);
    }

    private void logRequest(String method, String requestId, String req) {
        String flow = method + "::" + requestId + "::" + "\n请求包(req params) ==> " + req;
        logger.info(flow);
        saveRequestFlow(flow);
    }

    private void logResponse(String method, String requestId, String res) {
        String flow = method + "::" + requestId + "::" + "\n回包(res params ==> " + res;
        logger.info(flow);
        saveRequestFlow(flow);
    }

    public AnyPayBaseRequest getReqModel(Map<String, Object> data, String serviceCode, String requestId) {
        String sign = null;
        try {
            PrivateKey privateKey = (PrivateKey) AnyPayRsaUtil.getPrivateKeyFromFile(keyStorePath, rsaPwd);
            String str2Sign = data.entrySet()
                    .stream()
                    .map(entry -> entry.getKey() + "=" + entry.getValue())
                    .collect(Collectors.joining("&"));
            byte[] signBytes = AnyPaySignUtils.sign(privateKey, str2Sign.getBytes(StandardCharsets.UTF_8), signAldo);
            sign = new String(Base64.encodeBase64(signBytes), StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
        }

        AnyPayBaseRequest model = new AnyPayBaseRequest();
        model.setMchtNo(mchtNo);
        model.setRequestId(requestId);
        model.setServiceCode(serviceCode);
        model.setReqTime(AnyPayDateUtil.getCurrentDate(AnyPayDateUtil.YYYYMMDDHHMMSS));
        model.setVersion("1.0.0");
        model.setSign(sign);
        model.setData(data);
        return model;
    }

    public AnyPayBaseRespModel getResp(String resp) {
        return getResp(resp, false);
    }

    /**
     * 处理响应json数据
     * @param resp 响应json
     * @return
     */
    public AnyPayBaseRespModel getResp(String resp, boolean needDecryptData) {
        PublicKey publicKey = null;
        AnyPayBaseRespModel model = null;
        try {
            publicKey = (PublicKey) AnyPayRsaUtil.getPublicKeyFromFile(keyStorePath, rsaPwd);
            if (needDecryptData) {
                var anyPayBaseCallbackRespModel = ChannelJSONUtil.parseJson(resp, AnyPayBaseCallbackRespModel.class);
                var data = anyPayBaseCallbackRespModel.getData();
                String decryptedData = AnyPayAESUtil.base64CbcDecrypt(data.replace("\n", ""), aesKey);
                JSONObject jso= JSON.parseObject(decryptedData);
                model = AnyPayBaseRespModel.builder()
                        .data(jso)
                        .errorCode(anyPayBaseCallbackRespModel.getErrorCode())
                        .errorDesc(anyPayBaseCallbackRespModel.getErrorDesc())
                        .mchtNo(anyPayBaseCallbackRespModel.getMchtNo())
                        .requestId(anyPayBaseCallbackRespModel.getRequestId())
                        .respTime(anyPayBaseCallbackRespModel.getRespTime())
                        .resultCode(anyPayBaseCallbackRespModel.getResultCode())
                        .sign(anyPayBaseCallbackRespModel.getSign())
                        .version(anyPayBaseCallbackRespModel.getVersion())
                        .build();
            } else {
                model = ChannelJSONUtil.parseJson(resp, AnyPayBaseRespModel.class);
            }
        } catch (Exception e) {
            logger.fail(AnyPayConstant.ERROR_DEFAULT_CODE, "解析响应数据时加解密异常", e);
        }

        assert model != null;
        if (model.getResultCode() != 0) {
            var nestException = new AnyPayException(AnyPayConstant.ERROR_DEFAULT_CODE, model.getErrorCode());
            nestException.setRsp(model);
            throw new AnyPayException(AnyPayConstant.ERROR_DEFAULT_CODE,
                    "请求结果为失败: RequestId: " + model.getRequestId()  + "\n" +
                            "ErrorCode: " + model.getErrorCode() + "\n" +
                            "ErrorDesc: " + model.getErrorDesc() + "\n" +
                            "Data: " + ChannelJSONUtil.toJson(model.getData()),
                    nestException
            );
        }
        if(!model.getData().isEmpty()) {
            Map<String, Object> respData = model.getData();
            String sign = model.getSign();
            String str2Sign = (new TreeMap<>(respData)).entrySet()
                    .stream() .map(entry -> entry.getKey() + "=" + entry.getValue())
                    .collect(Collectors.joining("&"));
            byte[] ckSignBytes = Base64.decodeBase64(sign.getBytes(StandardCharsets.UTF_8));
            boolean checked = AnyPaySignUtils.verifySign(publicKey, str2Sign.getBytes(StandardCharsets.UTF_8),
                    ckSignBytes, signAldo);
            if (checked) {
                return model;
            } else {
                logger.fail(
                    AnyPayConstant.ENCRYPT_OR_SIGN_ERROR,
                    "响应数据验签失败（response verify sign fail) str2Sign => " + str2Sign
                );
            }
        } else {
            logger.info("响应data数据为空");
        }
        return null;
    }

    public AnyPayBaseRespModel http(String methodName, String requestId, Map<String, Object> data, String serviceCode) {
        var reqModel = getReqModel(data, serviceCode, requestId);
        var reqJson = ChannelJSONUtil.toJson(reqModel);
        logRequest(methodName, requestId, reqJson);
        try {
            var respJson = AnyPayOkHttpUtil.httpClientJsonPostReturnAsString(perfectPayUrl, reqJson, TIME_OUT);
            logResponse(methodName, requestId, respJson);
            return getResp(respJson);
        } catch (Exception e) {
            logger.fail(AnyPayConstant.ERROR_DEFAULT_CODE, e.getMessage(), e);
        }
        return null;
    }
}
